/*
 * Amkai Enterprise Solution (AES.) 
 * Copyright 2004-2008 by Amkai(TM), Inc.
 * www.amkai.com
 * 
 * Project: webservice
 * Package: com.amkai.aes.webservice.binary.objecthandler
 * File: RemittancePostingHandler.java
 * Created: 2008.10.07. 16:19:26
 * Author: polaj 
 * 
 */
package com.amkai.aes.webservice.binary.objecthandler;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;

import com.amkai.aes.aescommon.billing.BillingConstants;
import com.amkai.aes.aescommon.billing.DenialCodePairManager;
import com.amkai.aes.aescommon.billing.RemittancePostingSessionUtils;
import com.amkai.aes.aescommon.billing.ThinRemittancePostingSession;
import com.amkai.aes.aescommon.dictionary.ItemManager;
import com.amkai.aes.aescommon.dictionary.ThinPhysician;
import com.amkai.aes.aescommon.exceptions.AOException;
import com.amkai.aes.aescommon.ledger.managers.ChargeDebitManager;
import com.amkai.aes.aescommon.ledger.managers.ChargeManager;
import com.amkai.aes.aescommon.ledger.managers.PaymentManager;
import com.amkai.aes.aescommon.ledger.managers.TransferManager;
import com.amkai.aes.aescommon.ledger.managers.WriteOffManager;
import com.amkai.aes.aescommon.ledger.managers.details.FromToManager;
import com.amkai.aes.aescommon.ledger.utils.EntryUtils;
import com.amkai.aes.aescommon.registration.ThinInsuranceCarrier;
import com.amkai.aes.aescommon.service.binary.job.RemittancePostingJob;
import com.amkai.aes.aescommon.service.result.RemittancePostingResult;
import com.amkai.aes.aescommon.service.scheduling.TimeUtils;
import com.amkai.aes.generatedbeans.billinginterval.Batch;
import com.amkai.aes.generatedbeans.billingledger.Charge;
import com.amkai.aes.generatedbeans.billingledger.ChargeDebit;
import com.amkai.aes.generatedbeans.billingledger.Entry;
import com.amkai.aes.generatedbeans.billingledger.Payment;
import com.amkai.aes.generatedbeans.billingledger.TransactionKind;
import com.amkai.aes.generatedbeans.billingledger.Transfer;
import com.amkai.aes.generatedbeans.billingledger.WriteOff;
import com.amkai.aes.generatedbeans.billingledger.details.FromTo;
import com.amkai.aes.generatedbeans.insurance.InsuranceCarrier;
import com.amkai.aes.generatedbeans.organization.Department;
import com.amkai.aes.generatedbeans.passport.Passport;
import com.amkai.aes.generatedbeans.physstaff.Physician;
import com.amkai.aes.generatedbeans.remittanceposting.RemittancePostingRow;
import com.amkai.aes.generatedbeans.remittanceposting.RemittancePostingSearchInsuranceCarrierAssociation;
import com.amkai.aes.generatedbeans.remittanceposting.RemittancePostingSearchProviderAssociation;
import com.amkai.aes.generatedbeans.remittanceposting.RemittancePostingSession;
import com.amkai.aes.generatedbeans.remittanceposting.RemittancePostingTransactionProperties;
import com.amkai.aes.generatedmodels.billing.remittanceposting.RemittancePostingDomain;
import com.amkai.aes.server.billing.RemittancePostingManager;
import com.amkai.aes.server.billing.ledger.ServerLedgerEntryLoadManager;
import com.amkai.aes.server.manager.AbstractEntryCommitter;
import com.amkai.aes.server.session.ServiceSessionManager;
import com.amkai.aes.webservice.binary.JobObjectHandler;
import com.amkai.common.extensions.hibernate.config.SiteManager;
import com.amkai.common.extensions.hibernate.config.manager.SessionManager;
import com.amkai.rad.designerbase.beans.ChangedKind;

public class RemittancePostingHandler implements JobObjectHandler<RemittancePostingJob, Department, RemittancePostingResult> {

	protected static final Log log = LogFactory.getLog(RemittancePostingHandler.class);
	protected static final String TITLE = "Remittance Posting";
	@Override
	public RemittancePostingResult process(RemittancePostingJob job, Department dep) throws Exception {
		switch (job.getAction()) {
		case GET_REMITTANCE_POSTING_SESSIONS:	return getRemittancePostingSessions(job,dep);
		case RELOAD_REMITTANCE_POSTING_SESSION:	return reloadRemittancePostingSession(job, dep);
		case INIT_REMITTANCE_POSTING:	return initRemittancePosting(job,dep);
		case SEARCH:	return search(job,dep);
		case COMMIT_DOMAIN:	return commitDomain(job,dep);
		case SAVE_REMITTANCE_POSTING_ROW:	return saveRemittancePostingRow(job, dep);
		case APPLY_REMITTANCE_POSTING:
			ApplyManager manager = new ApplyManager();
			return manager.applyRemittancePosting(job, dep);
		case CLOSE_REMITTANCE_POSTING_SESSION:	return closeRemittancePostingSession(job.getRemittancePostingSession(),dep);
		case GET_REMITTANCE_POSTING_ROWS:	return getRemittancePostingRows(job, dep);
		case HAS_SESSION_UNAPPLIED_TRANSACTIONS: return hasSessionUnappliedTransactions(job, dep);
		
		// development
		case GET_REMITTANCE_POSTING_SESSIONS2:	return getRemittancePostingSessions2(job);
		case CREATE_REMITTANCE_POSTING_SESSION:	return createRemittancePostingSession(job);
		case SAVE_REMITTANCE_POSTING_SESSION:	return saveRemittancePostingSession(job);
		}
		return null;
	}
	
	protected RemittancePostingResult getRemittancePostingSessions(RemittancePostingJob job,Department dep) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			String query = "FROM RemittancePostingSession rps " +
			"LEFT JOIN FETCH rps.batch b " +
			"LEFT JOIN FETCH b.period p " +
			"WHERE rps.baseOrganization=:bo ";
			if (job.isShowOnlyOpenSessions())
				query += "AND rps.closed IS NULL";
			log.debug("Loading Sessions");
			List<RemittancePostingSession> sessions = sm.execute(query)
				.setParameter("bo", dep).list();
			result.setRemittancePostingSessions(sessions);
		}
		catch (Exception ex) {
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	private List<InsuranceCarrier> getInsuranceCarriers(SessionManager sm, Department department) throws Exception {
		long start = System.currentTimeMillis();
		List<InsuranceCarrier> carriers = sm.execute("FROM InsuranceCarrier ic " +
				"WHERE ic.active=true " +
				"AND ic.baseOrganization=:bo " +
				"ORDER BY ic.name ASC")
			.setParameter("bo", department)
			.list();
		
		for (InsuranceCarrier ic : carriers) {
			ItemManager.loadItem(ic.getMainInsurancePlan().getDefaultPaymentJournalCode());
			ItemManager.loadItem(ic.getMainInsurancePlan().getDefaultWriteoffJournalCode());
			ItemManager.loadItem(ic.getMainInsurancePlan().getDefaultWriteoff2JournalCode());
			ItemManager.loadItem(ic.getMainInsurancePlan().getDefaultDebitJournalCode());
		}
		log.debug(String.format("Insurance Carriers have been selected in %d msec", System.currentTimeMillis()-start));
		return carriers;
	}

	private List<Physician> getProviders(SessionManager sm, Department department) throws Exception {
		long start = System.currentTimeMillis();
		List<Physician> providers = sm.execute("FROM Physician p " +
				"WHERE p.isProvider=true " +
				"AND p.baseOrganization=:bo " +
				"ORDER BY p.name ASC")
			.setParameter("bo", department)
			.list();
		
		log.debug(String.format("Physicians have been selected in %d msec", System.currentTimeMillis()-start));
		return providers;
	}
	
	protected RemittancePostingResult initRemittancePosting(RemittancePostingJob job, Department moduleDepartment) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			result.setInsuranceCarriers(getInsuranceCarriers(sm, moduleDepartment));
			result.setProviders(getProviders(sm, moduleDepartment));

			RemittancePostingSession session = job.getRemittancePostingSession();
//			result.setDomain(buildDomain(sm, session, dep, true));
			result.setRemittancePostingSession(reloadRemittancePostingSession(sm, session, moduleDepartment, true));
		}
		catch (Exception ex) {
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}

	protected RemittancePostingResult reloadRemittancePostingSession(RemittancePostingJob job, Department dep) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			RemittancePostingSession session = reloadRemittancePostingSession(sm, job.getRemittancePostingSession(), dep, false);
			result.setRemittancePostingSession(session);
		}
		catch (Exception ex) {
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected RemittancePostingSession reloadRemittancePostingSession(SessionManager sm, RemittancePostingSession session, Department moduleDepartment, boolean init) throws Exception {
		long start = System.currentTimeMillis();
		if (session.getId() != null) {
			session = (RemittancePostingSession)sm.execute(
					"FROM RemittancePostingSession s " +
					"JOIN FETCH s.searchProperties sp " +
					"JOIN FETCH s.batch batch " +
					"JOIN FETCH batch.creator " +
					"LEFT JOIN FETCH batch.closer " +
					"LEFT JOIN FETCH batch.expecteds " +
					"JOIN FETCH batch.period " +
					"JOIN FETCH batch.period.baseOrganization " +
					"JOIN FETCH s.baseOrganization bo " +
					"JOIN FETCH s.owner " +
					"WHERE s.id=:id")
				.setParameter("id", session.getId())
				.uniqueResult();
			
			log.debug(String.format("Remittance Posting Session query runs in %d msec (%s rows)", System.currentTimeMillis()-start, session.getRemittancePostingRows().size()));
			start = System.currentTimeMillis();
			loadRemittancePostingSession(session);


			log.debug(String.format("Session has been reloaded in %d msec", System.currentTimeMillis()-start));
/*			
			List<RemittancePostingRow> rows = sm.execute("FROM RemittancePostingRow r " +
					"WHERE r.remittancePostingSession=:session")
					.setEntity("session", session)
					.setMaxResults(50)
					.list();
			
			session.getRemittancePostingRows().clear();
*/			
//			for (RemittancePostingRow row : session.getRemittancePostingRows()) {
//				loadRemittancePostingRow(sm, row, init, moduleDepartment);
//			}
//			session.getRemittancePostingRows().addAll(rows);
			
		}
		return session;
	}
/*	
	protected RemittancePostingDomain buildDomain(SessionManager sm,RemittancePostingSession session, Department dep, boolean init) throws Exception {
		RemittancePostingDomain domain = new RemittancePostingDomain();
		domain.setRemittancePostingSession(reloadRemittancePostingSession(sm, session, dep, init));
		return domain;
	}
*/	
	protected RemittancePostingResult search(RemittancePostingJob job,Department dep) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			
			List<Physician> providers = new ArrayList<Physician>();
			for (RemittancePostingSearchProviderAssociation assoc : job.getSearchProperties().getRemittancePostingSearchProviderAssociations()) {
				providers.add(assoc.getProvider());
			}
			
			List<InsuranceCarrier> carriers = new ArrayList<InsuranceCarrier>();
			for (RemittancePostingSearchInsuranceCarrierAssociation assoc : job.getSearchProperties().getRemittancePostingSearchInsuranceCarrierAssociations()) {
				carriers.add(assoc.getInsuranceCarrier());
			}
			
			StringBuffer query = new StringBuffer(
					"FROM Charge c " +
					"WHERE c.isActive=true " +
					"AND c.batch.period.baseOrganization=:bo " +
//					"AND size(c.remittancePostingRows) = 0 " +
					"AND c.fromEntry.insurancePolicy IS NOT NULL ");
			
			if (carriers.size() > 0)
				query.append("AND c.fromEntry.insurancePolicy.insurancePlan.insuranceCarrier IN (:carriers) ");
			
			if (providers.size() > 0)
				query.append("AND c.provider IN (:providers) ");
			
			switch (job.getSearchProperties().getDateSearchKind()) {
			case TRANSACTION_DATE:
				query.append("AND c.transactionDate BETWEEN :from AND :thru ");
//				query.append("AND :from <= c.transactionDate AND c.transactionDate <= :thru ");
				break;
			case BILLED_DATE:
				query.append("AND c.currentBillCharge.bill.created BETWEEN :from AND :thru ");
//				query.append("AND :from <= c.currentBillCharge.bill.created AND c.currentBillCharge.bill.created <= :thru ");
				break;
			}
			if (job.getSearchProperties().isOpenChargesOnly())
				query.append("AND c.due > 0");

			Calendar from = Calendar.getInstance();
			// truncate the time part
			from.setTimeInMillis(TimeUtils.truncateTime(job.getSearchProperties().getFrom().getTime()));
			
			Calendar thru = Calendar.getInstance();
			// truncate the time part
			thru.setTimeInMillis(TimeUtils.truncateTime(job.getSearchProperties().getThru().getTime()));
			// set the end of the day
			thru.add(Calendar.DATE, 1);
			thru.add(Calendar.SECOND,-1);
			
			query.append("ORDER BY c.id ASC");
			
			log.debug(query.toString());
			Query q = sm.execute(query.toString())
			.setParameter("from", from.getTime())
			.setParameter("thru", thru.getTime())
			.setParameter("bo", dep);
			
			if (carriers.size()>0)
				q.setParameterList("carriers", carriers);
			
			if (providers.size()>0)
				q.setParameterList("providers", providers);

			int max = 0;
			if (job.getSearchProperties().getMaxSearchResults() != null)
				max = job.getSearchProperties().getMaxSearchResults().intValue();
			
			if (max > 0)
				q.setMaxResults(max+1);
			
			List<Charge> charges = q.list();
			
			if (max > 0 && charges.size() > max) {
				result.setExistsMoreResult(true);
				if (charges.size() > 0)
					charges.remove(charges.size()-1);
			}
				
			for (Charge charge : charges)
				ServerLedgerEntryLoadManager.loadEntry(charge, sm, false, dep);
			
			result.setCharges(charges);
		}
		catch (Exception ex) {
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected RemittancePostingResult commitDomain(RemittancePostingJob job, Department moduleDepartment) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			RemittancePostingManager rpm = new RemittancePostingManager(sm, moduleDepartment);
			sm.openTransaction();
			
			rpm.saveRemittancePostingSession(job.getRemittancePostingSession());
			
			List<RemittancePostingRow> updatedRows = new ArrayList<RemittancePostingRow>();
			if (job.isCommitRemittancePostingRows()) {
				for (RemittancePostingRow row : job.getChangedRows()) {
					row.setRemittancePostingSession(job.getRemittancePostingSession());
					rpm.saveRemittancePostingRow(row);
					updatedRows.add(row);
					row.getCharge().getId();
				}
			}
			
			sm.commitTransaction();
			
			result.setRemittancePostingRows(updatedRows);
			result.setRemittancePostingSession(job.getRemittancePostingSession());
		}
		catch (Exception ex) {
			result.setException(ex);
			sm.rollbackTransaction();
			log.error(TITLE, ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected RemittancePostingResult saveRemittancePostingRow(RemittancePostingJob job, Department dep) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			sm.openTransaction();
			RemittancePostingRow row = job.getRemittancePostingRow();
			
			RemittancePostingManager rpm = new RemittancePostingManager(sm, dep);
			rpm.saveRemittancePostingSession(row.getRemittancePostingSession());
			rpm.saveRemittancePostingRow(row);
			
			result.setRemittancePostingRow(row);
			sm.commitTransaction();
		}
		catch (Exception ex) {
			result.setException(ex);
			sm.rollbackTransaction();
			log.error(TITLE, ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected RemittancePostingResult closeRemittancePostingSession(RemittancePostingSession session,Department dep) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			sm.openTransaction();
			RemittancePostingManager rpm = new RemittancePostingManager(sm, dep);
			rpm.closeRemittancePostingSession(session);			
			sm.commitTransaction();
		}
		catch (Exception ex) {
			sm.rollbackTransaction();
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected Entry searchEntry(Charge charge, Long id, TransactionKind kind) throws Exception {
		for (Entry child : charge.getChildEntries()) {
			if (child.getTransactionKind() == kind && id != null && id.equals(child.getId())) {
				return child;
			}
		}
		throw new AOException(TITLE,String.format("%s not found", kind.toString()),String.format("%s ID: %d", kind.toString(), id));
	}
	
	private void loadRemittancePostingSession(RemittancePostingSession session) {
		session.getTableColumnSorts().size();
		session.getTransactionProperties().getCheckNumber();
		session.getBatch().getPeriod().getArDelta();
		ItemManager.loadItem(session.getTransactionProperties().getPaymentJournalCode());
		ItemManager.loadItem(session.getTransactionProperties().getWriteOff1JournalCode());
		ItemManager.loadItem(session.getTransactionProperties().getWriteOff2JournalCode());
		ItemManager.loadItem(session.getTransactionProperties().getDebitJournalCode());
		ItemManager.loadItem(session.getTransactionProperties().getTransferJournalCodeToOther());
		ItemManager.loadItem(session.getTransactionProperties().getTransferJournalCodeToPG());
		ItemManager.loadItem(session.getTransactionProperties().getTransferJournalCodeToSI());
		ItemManager.loadItem(session.getTransactionProperties().getTransferJournalCodeToTI());
		DenialCodePairManager.loadDenialCodePair(session.getTransactionProperties().getWriteOff1DenialCodePair());
		DenialCodePairManager.loadDenialCodePair(session.getTransactionProperties().getWriteOff2DenialCodePair());
		DenialCodePairManager.loadDenialCodePair(session.getTransactionProperties().getDebitDenialCodePair());
		for (RemittancePostingSearchInsuranceCarrierAssociation assoc : session.getSearchProperties().getRemittancePostingSearchInsuranceCarrierAssociations()) {
			assoc.getInsuranceCarrier().getName();
		}
		for (RemittancePostingSearchProviderAssociation assoc : session.getSearchProperties().getRemittancePostingSearchProviderAssociations()) {
			assoc.getProvider().getPerson().getDisplayName();
		}
		session.getRemittancePostingRows().size();
		if (session.getLinkInfo() != null) {
			session.getLinkInfo().getComment();
		}
	}
	
	private void loadRemittancePostingRow(SessionManager sm, RemittancePostingRow row, boolean init, Department moduleDepartment) throws Exception {
		ItemManager.loadItem(row.getPaymentJournalCode());
		ItemManager.loadItem(row.getWriteOff1JournalCode());
		ItemManager.loadItem(row.getWriteOff2JournalCode());
		ItemManager.loadItem(row.getDebitJournalCode());
		ItemManager.loadItem(row.getTransferJournalCode());
		DenialCodePairManager.loadDenialCodePair(row.getWriteOff1DenialCodePair());
		DenialCodePairManager.loadDenialCodePair(row.getWriteOff2DenialCodePair());
		DenialCodePairManager.loadDenialCodePair(row.getDebitDenialCodePair());
		FromToManager.loadFromTo(row.getTransferTo());
		
		ServerLedgerEntryLoadManager.loadEntry(row.getCharge(), sm, false, moduleDepartment);
		if (init) {
			if (row.getPayment() != null) {
				row.setPaymentAmount(row.getPayment().getAmount());
				row.setPaymentJournalCode(row.getPayment().getJournalCode());
				row.setPaymentPrintOnStatement(row.getPayment().isPrintOnStatement());
				row.setPaymentNote(row.getPayment().getNote());
			}
			if (row.getWriteOff1() != null) {
				row.setWriteOff1Amount(row.getWriteOff1().getAmount());
				row.setWriteOff1JournalCode(row.getWriteOff1().getJournalCode());
				row.setWriteOff1DenialCodePair(row.getWriteOff1().getDenialCodePair());
				row.setWriteOff1PrintOnStatement(row.getWriteOff1().isPrintOnStatement());
				row.setWriteOff1Note(row.getWriteOff1().getNote());
			}
			if (row.getWriteOff2() != null) {
				row.setWriteOff2Amount(row.getWriteOff2().getAmount());
				row.setWriteOff2JournalCode(row.getWriteOff2().getJournalCode());
				row.setWriteOff2DenialCodePair(row.getWriteOff2().getDenialCodePair());
				row.setWriteOff2PrintOnStatement(row.getWriteOff2().isPrintOnStatement());
				row.setWriteOff2Note(row.getWriteOff2().getNote());
			}
			if (row.getDebit() != null) {
				row.setDebitAmount(row.getDebit().getAmount());
				row.setDebitJournalCode(row.getDebit().getJournalCode());
				row.setDebitDenialCodePair(row.getDebit().getDenialCodePair());
				row.setDebitPrintOnStatement(row.getDebit().isPrintOnStatement());
				row.setDebitNote(row.getDebit().getNote());
			}
			if (row.getTransfer() != null) {
				row.setTransferJournalCode(row.getTransfer().getJournalCode());
			}
		}
	}
	
	private RemittancePostingResult getRemittancePostingRows(RemittancePostingJob job, Department moduleDepartment) throws Exception {
		long start = System.currentTimeMillis();
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			List<RemittancePostingRow> rows = sm.execute("FROM RemittancePostingRow r " +
					"WHERE r.remittancePostingSession.id=:session_id " +
					"ORDER BY r.charge.id ASC")
					.setParameter("session_id", job.getRemittancePostingSessionID())
					.setFirstResult(job.getFirstResult())
					.setMaxResults(job.getMaxResult())
					.list();
			
			if (rows.size() > 0)
				loadRemittancePostingSession(rows.get(0).getRemittancePostingSession());
			
			for (RemittancePostingRow row : rows)
				loadRemittancePostingRow(sm, row, true, moduleDepartment);
			
			result.setRemittancePostingRows(rows);
			log.debug(String.format("Remittance Posting Rows from %d to %d have been loaded in %d msec", job.getFirstResult(), job.getFirstResult()+job.getMaxResult(), System.currentTimeMillis()-start));
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected RemittancePostingResult hasSessionUnappliedTransactions(RemittancePostingJob job, Department moduleDepartment) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			RemittancePostingSession session = sm.get(RemittancePostingSession.class, job.getRemittancePostingSessionID());
			result.setHasSessionUnappliedTransactions(hasSessionUnappliedTransactions(session));			
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected boolean hasSessionUnappliedTransactions(RemittancePostingSession session) {
		for (RemittancePostingRow row : session.getRemittancePostingRows()) {
			if (row.getPaymentAmount() != null && row.getPayment() == null)
				return true;
			if (row.getWriteOff1Amount() != null && row.getWriteOff1() == null)
				return true;
			if (row.getWriteOff2Amount() != null && row.getWriteOff2() == null)
				return true;
			if (row.getDebitAmount() != null && row.getDebit() == null)
				return true;
		}
		return false;
	}
	
	// --------------------------------- development
	
	private RemittancePostingResult initRemittancePosting(RemittancePostingJob job) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			RemittancePostingDomain domain = new RemittancePostingDomain();
			domain.setRemittancePostingSession(sm.get(RemittancePostingSession.class, job.getRemittancePostingSessionID()));
			domain.loadProperties();
			result.setInsuranceCarriers(getInsuranceCarriers(sm, job.getModuleDepartment()));
			result.setProviders(getProviders(sm, job.getModuleDepartment()));
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	private RemittancePostingResult getRemittancePostingSessions2(RemittancePostingJob job) throws Exception {
		long start = System.currentTimeMillis();
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			String query = "FROM RemittancePostingSession rps " +
				"LEFT JOIN FETCH rps.batch b " +
				"LEFT JOIN FETCH b.period p " +
				"WHERE rps.baseOrganization=:bo ";
			if (job.isShowOnlyOpenSessions())
				query += "AND rps.closed IS NULL";
			List<RemittancePostingSession> l = sm.execute(query).setParameter("bo", job.getModuleDepartment()).list();
			List<ThinRemittancePostingSession> sessions = new ArrayList<ThinRemittancePostingSession>();
			for (RemittancePostingSession session : l) {
				sessions.add(ThinRemittancePostingSession.create(session));
			}
			result.setThinRemittancePostingSessions(sessions);
			log.debug(String.format("Thin Remittance Posting Sessions have been loaded in %d msec", System.currentTimeMillis()-start));
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	private RemittancePostingResult createRemittancePostingSession(RemittancePostingJob job) throws Exception {
		long start = System.currentTimeMillis();
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			RemittancePostingSession session = RemittancePostingSessionUtils.create(ServiceSessionManager.getConnection().getRuntimePassport());
			
			session.setBatch(sm.get(Batch.class, job.getBatchID()));
			
			RemittancePostingDomain domain = new RemittancePostingDomain();
			domain.setRemittancePostingSession(session);
			
			domain.loadProperties();

			List<ThinInsuranceCarrier> thinInsuranceCarriers = new ArrayList<ThinInsuranceCarrier>();
			for (InsuranceCarrier ic : getInsuranceCarriers(sm, job.getModuleDepartment())) {
				thinInsuranceCarriers.add(ThinInsuranceCarrier.create(ic));
			}
			
			List<ThinPhysician> thinProviders = new ArrayList<ThinPhysician>();
			for (Physician p : getProviders(sm, job.getModuleDepartment())) {
				thinProviders.add(ThinPhysician.create(p));
			}
			result.setThinInsuranceCarriers(thinInsuranceCarriers);
			result.setThinProviders(thinProviders);
			result.setDomain(domain);
			log.debug(String.format("Remittance Posting Session has been created in %d msec", System.currentTimeMillis()-start));
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	private RemittancePostingResult saveRemittancePostingSession(RemittancePostingJob job) throws Exception {
		RemittancePostingResult result = new RemittancePostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			RemittancePostingManager rpm = new RemittancePostingManager(sm, job.getModuleDepartment());
			sm.openTransaction();
			
			RemittancePostingSession session = job.getRemittancePostingSession(); 
			rpm.saveRemittancePostingSession(session);
			
			List<RemittancePostingRow> updatedRows = new ArrayList<RemittancePostingRow>();
			for (RemittancePostingRow row : job.getChangedRows()) {
				row.setRemittancePostingSession(job.getRemittancePostingSession());
				rpm.saveRemittancePostingRow(row);
				updatedRows.add(row);
				row.getCharge().getId();
			}
			
			sm.commitTransaction();
			
			result.setRemittancePostingRows(updatedRows);
			
			RemittancePostingDomain domain = new RemittancePostingDomain();
			domain.setRemittancePostingSession(session);
			result.setDomain(domain);
		}
		catch (Exception ex) {
			sm.rollbackTransaction();
			log.error(TITLE, ex);
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	class ApplyManager extends AbstractEntryCommitter {
		
		public RemittancePostingResult applyRemittancePosting(RemittancePostingJob job, Department moduleDepartment) throws Exception {
			RemittancePostingResult result = new RemittancePostingResult();
			SessionManager sm = SiteManager.Start();
			try {
				RemittancePostingManager rpm = new RemittancePostingManager(sm, moduleDepartment);
				List<RemittancePostingRow> updatedRows = new ArrayList<RemittancePostingRow>();
				sm.openSession();
				sm.openTransaction();
				rpm.saveRemittancePostingSession(job.getRemittancePostingSession());
				sm.commitTransaction();
				
//				RemittancePostingSaveManager manager = RemittancePostingSaveManager.initialize(dep);
				
//				manager.applyRemittancePostingSession(job.getDomain().getRemittancePostingSession());
				RemittancePostingSession session = job.getRemittancePostingSession();
				RemittancePostingTransactionProperties tp = session.getTransactionProperties();
				try {
					FromTo chargeFromTo = null;
					
					Passport owner = ServiceSessionManager.getConnection().getRuntimePassport();
					owner = sm.get(Passport.class, owner.getId());
					Charge charge  = null;
					for (RemittancePostingRow row : session.getRemittancePostingRows()) {
						
						try {
//							if (row.getChangedKind() == ChangedKind.CHANGED) {
								sm.openTransaction();
								boolean transactionCreated = false;
		
								charge = sm.get(Charge.class, row.getCharge().getId());
								ServerLedgerEntryLoadManager.getChildEntries(charge);
								ChargeManager cm = new ChargeManager(charge);
								chargeFromTo = cm.getFromEntry();
								if (row.getTransfer() != null)
									chargeFromTo = row.getTransfer().getFromEntry();
								
								BigDecimal transfer_amount = cm.getDue();
								
								// payment amount is set
								if (row.getPaymentAmount() != null) {
									transactionCreated = true;
									// payment not exists
									PaymentManager<Payment> pm = null;
									if (row.getPayment() == null) {
										pm = new PaymentManager(cm.createPayment(owner));
										pm.setBatch(session.getBatch());
										pm.setFromEntry(chargeFromTo,true);
										row.setPayment(pm.getEntry());
										pm.getEntry().putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 1);
									}
									else {
										pm = new PaymentManager((Payment)searchEntry(charge, row.getPayment().getId(), TransactionKind.PAYMENT));
										pm.getEntry().setChangedKind(ChangedKind.CHANGED);
									}
									transfer_amount = transfer_amount.subtract(row.getPaymentAmount().subtract(pm.getAmount()));
									pm.setAmount(row.getPaymentAmount());
									pm.setTransactionDate(session.getTransactionProperties().getTransactionDate());
									pm.setJournalCode(row.getPaymentJournalCode());
									pm.setNote(row.getPaymentNote());
									pm.setPrintOnStatement(row.isPaymentPrintOnStatement());
									pm.setPaymentType(tp.getPaymentType());
									pm.setCheckNumber(tp.getCheckNumber());
									pm.setFreeText(tp.getFreePaymentType());
									row.setPayment(pm.getEntry());
								}
								
								
								if (row.getWriteOff1Amount() != null) {
									transactionCreated = true;
									WriteOffManager<WriteOff> wom = null;
									if (row.getWriteOff1() == null) {
										wom = new WriteOffManager(cm.createWriteOff(owner));
										wom.setBatch(session.getBatch());
										wom.setFromEntry(chargeFromTo,true);
										row.setWriteOff1(wom.getEntry());
										wom.getEntry().putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 2);
									}
									else {
										wom = new WriteOffManager((WriteOff)searchEntry(charge, row.getWriteOff1().getId(), TransactionKind.WRITEOFF));
										wom.getEntry().setChangedKind(ChangedKind.CHANGED);
									}
									transfer_amount = transfer_amount.subtract(row.getWriteOff1Amount().subtract(wom.getAmount()));
									wom.setAmount(row.getWriteOff1Amount());
									wom.setDenialCodePair(row.getWriteOff1DenialCodePair());
									wom.setTransactionDate(session.getTransactionProperties().getTransactionDate());
									wom.setJournalCode(row.getWriteOff1JournalCode());
									wom.setNote(row.getWriteOff1Note());
									wom.setPrintOnStatement(row.isWriteOff1PrintOnStatement());
									row.setWriteOff1(wom.getEntry());
								}
								
								if (row.getWriteOff2Amount() != null) {
									transactionCreated = true;
									WriteOffManager<WriteOff> wom = null;
									if (row.getWriteOff2() == null) {
										wom = new WriteOffManager(cm.createWriteOff(owner));
										wom.setBatch(session.getBatch());
										wom.setFromEntry(chargeFromTo,true);
										row.setWriteOff2(wom.getEntry());
										wom.getEntry().putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 3);
									}
									else {
										wom = new WriteOffManager((WriteOff)searchEntry(charge, row.getWriteOff2().getId(), TransactionKind.WRITEOFF));
										wom.getEntry().setChangedKind(ChangedKind.CHANGED);
									}
									transfer_amount = transfer_amount.subtract(row.getWriteOff2Amount().subtract(wom.getAmount()));
									wom.setAmount(row.getWriteOff2Amount());
									wom.setDenialCodePair(row.getWriteOff2DenialCodePair());
									wom.setTransactionDate(session.getTransactionProperties().getTransactionDate());
									wom.setJournalCode(row.getWriteOff2JournalCode());
									wom.setNote(row.getWriteOff2Note());
									wom.setPrintOnStatement(row.isWriteOff2PrintOnStatement());
									row.setWriteOff2(wom.getEntry());
								}
								
								if (row.getDebitAmount() != null) {
									transactionCreated = true;
									ChargeDebitManager<ChargeDebit> cdm = null;
									if (row.getDebit() == null) {
										cdm = new ChargeDebitManager(cm.createDebit(owner));
										cdm.setBatch(session.getBatch());
										cdm.setFromEntry(chargeFromTo);
										row.setDebit(cdm.getEntry());
										cdm.getEntry().putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 4);
									}
									else {
										cdm = new ChargeDebitManager((ChargeDebit)searchEntry(charge, row.getDebit().getId(), TransactionKind.CHARGE_DEBIT));
										cdm.getEntry().setChangedKind(ChangedKind.CHANGED);
									}
									transfer_amount = transfer_amount.add(row.getDebitAmount().subtract(cdm.getAmount()));
									cdm.setAmount(row.getDebitAmount());
									cdm.setTransactionDate(session.getTransactionProperties().getTransactionDate());
									cdm.setJournalCode(row.getDebitJournalCode());
									cdm.setNote(row.getDebitNote());
									cdm.setPrintOnStatement(row.isDebitPrintOnStatement());
									cdm.setDenialCodePair(row.getDebitDenialCodePair());
									row.setDebit(cdm.getEntry());
								}
								
								charge.setPatientResponsibility(row.getPatientResponsibility());
								TransferManager<Transfer> tm = null;
								if (row.getTransfer() == null) {
									if (row.isTransferFlag() && !charge.getFromEntry().equals(row.getTransferTo())) {
										tm = new TransferManager(cm.createTransfer(owner));
										tm.setBatch(session.getBatch());
										row.setTransfer(tm.getEntry());
										tm.setFromEntry(FromToManager.clone(cm.getFromEntry()));
										tm.setToEntry(row.getTransferTo());
										tm.setJournalCode(row.getTransferJournalCode());
										tm.setTransactionDate(session.getTransactionProperties().getTransactionDate());
										tm.setAmount(transfer_amount);
										tm.getEntry().putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 5);
										cm.setFromByTransfer(tm);
										//AES-8380
										//AES-3513
										//AES-10419 should clear the bill date when transferred to an Insurance
										//if (cm.getCurrentBillCharge() != null && cm.getDue().doubleValue() > 0)
										switch (cm.getFromEntry().getKind()) {
										case PRIMARY_INSURANCE:
										case SECONDARY_INSURANCE:
										case TERTIARY_INSURANCE:
											cm.setCurrentBillCharge(null);
										case PRIMARY_GUARANTOR:
										case SECONDARY_GUARANTOR:
										}
										// AES-9164
										cm.setLastTransferDate(tm.getTransactionDate());
										row.setTransfer(tm.getEntry());
									}	
								}
								else {
									tm = new TransferManager((Transfer)searchEntry(charge, row.getTransfer().getId(), TransactionKind.TRANSFER));
									tm.getEntry().setChangedKind(ChangedKind.CHANGED);
									tm.setToEntry(row.getTransferTo());
									tm.setJournalCode(row.getTransferJournalCode());
									cm.setFromByTransfer(tm);
									tm.setAmount(transfer_amount);
									row.setTransfer(tm.getEntry());
								}
								
								charge.setGenerateClaim(row.getGenerateClaim());
								charge.getChargeAdditionalInformation().setDocumentNeeded(row.isDocumentNeeded());
								charge.getChargeAdditionalInformation().setExcludeFromEMC(row.isExcludeFromEMC());
								
								charge.setOriginalClaimReferenceNumber(row.getOriginalReferenceNumber());
								charge.setResubmissionCode(row.getResubmissionCode());
								
								charge.getChargeAdditionalInformation().setAllowedAmount(row.getAllowedAmount());
								charge.getChargeAdditionalInformation().setDeductibleAmount(row.getDeductibleAmount());
								charge.setCopay(row.getCopay());
								charge.getChargeAdditionalInformation().setCoinsurance(row.getCoins());
								
								charge.setPatientAmountPaid(row.getPatientPaid());
			
								EntryUtils.setChanged(charge);
								saveEntry(sm, true, charge, moduleDepartment, owner);
								if (transactionCreated)
									row.setApplied(new Date());
//								row.setChangedKind(ChangedKind.CHANGED);
//								sm.saveRAD(row);
								rpm.saveRemittancePostingRow(row);
								if (transactionCreated) {
									updatedRows.add(row);
								}
								sm.commitTransaction();
//							} // Changed
						}
						catch (Exception ex) {
							sm.rollbackTransaction();
							throw ex;
						}
					}//for
				}
				finally {
					sm.openTransaction();
					recalculateFields(sm, moduleDepartment);
					sm.commitTransaction();
				}

				sm.closeSession();
				sm.clear();
				sm.openSession();
//				result.setDomain(buildDomain(sm, job.getDomain().getRemittancePostingSession(), moduleDepartment, true));
				session = reloadRemittancePostingSession(sm, session, moduleDepartment, true); 
				result.setRemittancePostingSession(session);
				List<RemittancePostingRow> l = new ArrayList<RemittancePostingRow>();
				for (RemittancePostingRow row : session.getRemittancePostingRows()) {
					row.setCharge(sm.get(Charge.class, row.getCharge().getId()));
					loadRemittancePostingRow(sm, row, false, moduleDepartment);
					l.add(row);
				}
				result.setRemittancePostingRows(l);
//				sm.closeSession();
			}
			catch (Exception ex) {
				result.setException(ex);
				log.error(TITLE, ex);
			}
			finally {
				sm.closeSession();
			}
			return result;
		}
	}
}
